package com.ms.jsf.omnifaces;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * User: gmc
 * Date: 19/03/13
 */
public class SelectItemsUtils {

    protected final static Logger LOG = LoggerFactory.getLogger(SelectItemsUtils.class);

    private SelectItemsUtils() {
    }

    /**
     * Finds an object value in the {@link SelectItem} instances associated with the given component by means of matching its converted value with
     * the given string value.
     *
     * @param context
     * @param component the component with which {@link SelectItem}s should be associated that are used to search in.
     * @param value     a string that should match the string representation of one of the values held by the {@link SelectItem}s.
     * @param converter the faces {@link Converter} used to generate String representations for the values held by the {@link SelectItem}s.
     * @return the Object representation of the value where its string representation matches the input value.
     */
    public static Object findValueByStringConversion(FacesContext context, UIComponent component, String value, Converter converter) {
        return findValueByStringConversion(context, component, SelectItemsCollector.collectFromParent(context, component).iterator(), value, converter);
    }

    public static Object findValueByStringConversionForEnum(FacesContext context, UIComponent component, String value, Converter converter) {
        Enum enumObject = SelectItemsCollector.getEnumFromParent(context, component);

        try {
            java.lang.reflect.Method method = enumObject.getClass().getMethod("findByCodigo", String.class);
            Object enumObjResult = method.invoke(enumObject, value);
//            System.out.println("Result:"+enumObjResult);
            return enumObjResult;
        } catch (Throwable e) {
            LOG.error("Error findValueByStringConversionForEnum ", e);
        }
        throw new IllegalStateException(enumObject + ". No hay valores para:" + value);
    }

    private static Object findValueByStringConversion(FacesContext context, UIComponent component, Iterator<SelectItem> items, String value, Converter converter) {
        while (items.hasNext()) {
            SelectItem item = items.next();
            if (item instanceof SelectItemGroup) {
                SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
                if (!isEmpty(subitems)) {
                    Object object = findValueByStringConversion(context, component, new ArrayIterator(subitems), value, converter);
                    if (object != null) {
                        return object;
                    }
                }
            } else if (!item.isNoSelectionOption()) {
                Object itemValue = item.getValue();
                String convertedItemValue = converter.getAsString(context, component, itemValue);
                if (value == null ? convertedItemValue == null : value.equals(convertedItemValue)) {
                    return itemValue;
                }
            }
        }

        return null;
    }

    /**
     * Collects all values associated with all {@link SelectItem} instances associated with the given component.
     * <p/>
     * Note that values from recursively scanned {@link SelectItemGroup} instances are included.
     *
     * @param context
     * @param component the component with which {@link SelectItem} instances should be associated
     * @return List of all values hold by {@link SelectItem} instances
     */
    public static List<Object> collectAllValuesFromSelectItems(FacesContext context, UIComponent component) {
        List<Object> values = new ArrayList<Object>();
        collect(SelectItemsCollector.collectFromParent(context, component).iterator(), values);

        return values;
    }

    private static void collect(Iterator<SelectItem> items, List<Object> values) {
        while (items.hasNext()) {
            SelectItem item = items.next();
            if (item instanceof SelectItemGroup) {
                SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
                if (!isEmpty(subitems)) {
                    collect(new ArrayIterator(subitems), values);
                }
            } else if (!item.isNoSelectionOption()) {
                values.add(item.getValue());
            }
        }
    }

    public static boolean isEmpty(Object[] array) {
        return array == null || array.length == 0;
    }

    /**
     * Exposes an Array via an <code>Iterator</code>
     */
    static class ArrayIterator implements Iterator<SelectItem> {

        public ArrayIterator(SelectItem items[]) {
            this.items = items;
        }

        private SelectItem items[];
        private int index = 0;

        @Override
        public boolean hasNext() {
            return (index < items.length);
        }

        @Override
        public SelectItem next() {
            try {
                return (items[index++]);
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

}
